Skip to main content

Asynchronous programming

· 13 min read
0136yuntu

课代表来了(本视频所有概念也适用于其他异步编程的语言·): 

什么是异步编程

我们在执行一个长时间任务时,程序不需要等待,而是继续执行之后的代码。

     getUserPost();
// ...

直到程序执行完了之后再回来通知你,通常是以回调函数(callback)的形式。function(userPost){}就是回调函数。这种编程模式避免了程序的阻塞,提高了CPU的执行效率。尤其适用于IO密集的,例如经常需要进行网络操作,数据库访问的应用。以JS 为例介绍Promise, 以及新标准中优雅的async, await语法糖。

     getUserPost(function(userPost){
console.log(userPost);
});
// ...

JavaScript两种异步方式(单线程编程语言):

异步方法一

   - 传统回调函数 (Callback Function) 例如 setTimeOut , 在一个函数指定的时间后执行。这个函数本身会立刻返回。程序会紧接着执行之后的代码。而我们传入的回调函数则会等到预定的时间才会执行。需要主要的是JavaScript从设计之初就是单线程的,即便看上去这里的回调函数和主程序在并发执行,但他们都运行在同一个主线程上。 实际上主线程上还运行了其他代码,包括界面逻辑,网络请求,数据处理等等。

注释 这里的 ()=> {} 是箭头表达式,相当于函数定义的简化写法。

     setTimeout(()=>{   
console.log("好运!");
}3000);
console.log("紧接着执行我这条代码");
// ...
setTimeout 这个回调函数有一个明显的缺点,如果我们执行多个异步操作,我们的程序会一层一层的嵌套下去,可读性会变得非常差。
  • 回调函数缺点: 函数回调地狱 : 一个函数执行完在执行内部另外一个,一层一层嵌套, 视频1视频2

   - 单线程优点: 由于所有操作运行在一个线程中,无需考虑线程同步和资源竞争的开销,避免了线程之间的频繁切换和竞争问题,降低了开销

异步方法二

Promise (承诺) : 寓意这个去这个请求会在未来某一个时刻返回

Promise 解决了回调地狱的问题

JaveScript 中使用 Promise 的 API,fetch就是一个很好的例子。 例如: fetch 向服务器请求,返回Promise对象,渲染前端,如果可以使用then 进行接受 返回的结果

    fetch("https://www.woshishuaige.om").then((res)=>{
}) //它用来发起一个请求来获取服务器数据,我们可以用它来动态更新页面内容(也就是平常说的 AJAX-
// Asynchronous JavaScript and XML.

注释 https://jsonplaceholder.typicode.com 是一个用于测试假数据的API.

    fetch("https://jsonplaceholder.typicode.com/posts/1 ")  
fetch 访问上面哪个网站后,立刻返回一个 Promise {<pending>}对象, 承诺这个请求会在未来的某个时刻返回数据。
我们随后可以调用它的then 方法,并传递一个回调函数。 如果这个请求在未来成功完成,请求的结果也会以参数的形式(response)传递进来。
    fetch("https://jsonplaceholder.typicode.com/posts/1 ")
.then ((response)=>{})
但如果是这样,Promise和回调函数就没有区别了。其实Promise的优点在于它可以用一种链式结构(Chaining)将多个异步操作串联起来。比如下面程序的 response.json() 方法也会返回一个Promise. 它代表在未来的某个时刻将返回的数据转换成JSON格式。
    fetch("https://jsonplaceholder.typicode.com/posts/1 ")
.then ((response)=>response.json())
如果我们想等到它完成之后再执行其他的操作,我们可以在后面追加一个then.然后执行接下来的代码,比如将结果打印出来。Promise 的链式调用避免了代码的层层嵌套。即便我们有一个很长的链,代码也不过是向下方增长而非向右,因此可读性会提升很多。
    fetch("https://jsonplaceholder.typicode.com/posts/1 ")
.then ((response)=>response.json())
.then((json) => console.log(json));
使用异步操作的时候也可能会遇到错误。比如各种网络问题或返回的数据格式不正确等。如果我们要捕获这些错误,最简单的方法是附加一个 catch 在链式结构的末尾。如果之前任何一个阶段发生了错误,那么catch 将会被触发,而后的then()将不会执行。这和同步编程中遇到的 try...catch块很类似。
    fetch("https://jsonplaceholder.typicode.com/posts/1 ")
.then ((response)=>response.json())
.then((json) => {console.log(json);
})
.catch((error)=>{
console.error(error);
});
 Promise 还提供finally 方法。 它会在Promise 链结束之后调用。无论失败与否,我们可以在这里做一些清理工作。比如如果我们用到了加载动画,则可以在finally中关闭它。
    fetch("https://jsonplaceholder.typicode.com/posts/1 ")
.then ((response)=>response.json())
.then((json) => {console.log(json);
})
.catch((error)=>{
console.error(error);
})
.finally(()=>{
stopLoadingAnimation();
});

Promise优点:

我们可以用链式结构将多个异步操作串联起来如下

可以不断追加类似java8 stream,这样的链式调用避免了层层嵌套,一个人东西的出现一定是解决某类问题


fetch("https://www.woshishuaige.om").then((res)=>{

console.log(“我”)

}).then(res=>{

console.log(“是”)

}).then(res=>{

console.log(“你")

}).then(res=>{

console.log(“爹")

}).then(res=>{

console.log(“爹")

})

catch:

如果这样请求过程中某个戒断遇到错误,我们会触发catch,then将不会执行 ,类似 java 语法·


fetch("https://www.woshishuaige.om")
.then((res)=>{
console.log(“我”)
}).then(res=>{

console.log(“是”)

}).then(res=>{

console.log(“你")

}).then(res=>{

console.log(“爹")

}).then(res=>{

console.log(“爹")

}).catch(error=>{

console.log("如果程序报错了会输出我"+error)

})

``

finally

我们在请求过程中 如果有一段 必须要执行的代码可以放在 finally 中 ,学过java异常就是一样的道理

一般我们用来释放资源, 关闭声明请求,清理工作·

```js

fetch("https://www.woshishuaige.om").then((res)=>{

console.log(“我”)

}).then(res=>{

console.log(“是”)

}).then(res=>{

console.log(“你")

}).then(res=>{

console.log(“爹")

}).then(res=>{

console.log(“爹")

}).catch(error=>{

console.log("如果程序报错了会输出我"+error)

}).finally(()=>{

//

stopLoadingAnimation()

// Up 牛批

})

Javascript ECMA17 的新标准 async/ await

一句话概括:

语法糖,让异步操作变简单的,这两关键字基本同根同生, 被 async 修饰的 funcation 接受 请求必须用 await

首先我们要使用 async关键字将函数标记为异步函数。异步函数就是指返回值为Promise对象的函数。比如之前用到的fetch()就是一个异步函数。

async function f(){

}
f(); // 这个函数返回值永远是 Promise 对象。

在异步函数中我们可以调用其他的异步函数。不过我们不再需要使用then(), 而是使用一个更加简洁的await语法。await 会等待Promise完成之后直接返回最终的结果。所以下面程序的response已经是服务器返回的响应数据了。需要注意的是await虽然看上去会暂停函数的执行,但在执行的过程中,JavaScript同样可以处理其它的任务。比如更新界面,运行其他程序代码等。这是因为await底层是基于Promise和事件循环机制实现的。Promise + 事件循环(Event Loop).

async function f(){
const response = await fetc('https:///');
const json = await response.json();
console.log(json);
}
f();

await 使用时候的陷阱:

陷阱一: 下面的例子:如果我们分别去await这两个异步操作,虽然不存在逻辑错误,但这样写会打破这两个fetch()操作的并行。因为我们会等到第一个任务完成后才开始第二个任务。这里更高效的做饭时将所有Promise用 Promise.all 组合起来, 然后再去 await. 修改后的程序运行效率也会直接提升一倍。

async function f(){
const a = await fetc('https://.../post/1');
const b = await fetc('https://.../post/2');
}
f();
async function f(){
const promiseA = await fetc('https://.../post/1');
const promiseB = await fetc('https://.../post/2');

const [a, b]= await Promise.all([promiseA, promiseB]);
}
f();

陷阱二: 如果我们需要在循环中执行异步操作,时不能够直接调用forEach 或者 map 这一类方法的。尽管在下面的函数中写了await, 但这里的forEach 会立刻返回,它并不会等到所有的异步操作都执行完毕。

async function f(){
[1, 2, 3].forEach(async(i)=>{
await someAsyncOperation();
});
console.log("done")
}
f();

如果我们希望等待循环中的异步操作都 一 一 完成之后才继续执行,那我们还是应当使用传统的for循环。

async function f(){
for (let i of [1, 2, 3]){
await someAsyncOperation();
})
console.log("done");
}
f();

更进一步,如果我们想要循环中的所有操作都并发执行,一种更炫酷的方法时使用 for await. 这里的 for 循环依然会等到所有的异步操作都完成后才继续向后执行。

async funcation f(){

const promises =[

someAsyncOperator();
someAsyncOperator();
someAsyncOperator();

];
for await (let result of promises){
}
console.log("done");
}

陷阱三: 我们不能在全局或者普通函数中直接使用await关键字。await 只能被用在异步函数(async function)中。 如果我们想在最外层中使用await, 那么需要先定义一个异步函数,然后在函数体中使用它。

多个Promise 使用时会打破并行,

第一个执行完才执行第二个,高效做法:

Promise.all内可以传递多个promise对象

然后再使用await ,效率会提升一倍

const [a,b]= await Promise.all([promiseA,promiseB])

如果想使用 循环中的 所有操作都并发执行可以使用 for await


async funcation f(){

const promises =[

someAsyncOperator();

someAsyncOperator();

someAsyncOperator();

];

for await (let result of promises){



}



}

不能在全局和 普通函数 中使用 await关键字,只能被应用异步,所以想要使用 await 就必须 用 async修饰函数

所以说就跟你对象一样 ,必须相互配合才能完成一些事情

作者:0136云图 https://www.bilibili.com/read/cv13486386 出处:bilibili